#include "PbrtRendererInfo.h"

#include <argsFileParser.h>

#include <FnAttribute/FnDataBuilder.h>
#include <FnAsset/FnAssetSuite.h>

#include <sys/types.h>
#include <dirent.h>
#include <errno.h>
#include <string>
#include <vector>
#include <iostream>
#include <algorithm>


FnLogging::FnLog PbrtRendererInfo::_log("PbrtRendererInfo");


PbrtRendererInfo::PbrtRendererInfo()
{
    initShaders();
}

PbrtRendererInfo::~PbrtRendererInfo()
{
}

void PbrtRendererInfo::fillRenderMethods(
    std::vector<Foundry::Katana::RendererInfo::RenderMethod*>& renderMethods) const
{
    FnKat::RendererInfo::DiskRenderMethod*    diskRenderMethod;
    FnKat::RendererInfo::PreviewRenderMethod* previewRenderMethod;

    diskRenderMethod = new FnKat::RendererInfo::DiskRenderMethod();
    previewRenderMethod = new FnKat::RendererInfo::PreviewRenderMethod();

    previewRenderMethod->setDebugOutputSupported(true);
    previewRenderMethod->setDebugOutputFileType("pbrt");

    renderMethods.push_back(diskRenderMethod);
    renderMethods.push_back(previewRenderMethod);
}

void PbrtRendererInfo::fillRendererObjectNames(
    std::vector<std::string>& rendererObjectNames,
    const std::string& type,
    const std::vector<std::string>& typeTags) const
{
    rendererObjectNames.clear();

    if (type == kFnRendererObjectTypeShader)
    {
        if ( std::find(typeTags.begin(), typeTags.end(), std::string("surface")) != typeTags.end() )
        {
            getShaderNames(rendererObjectNames);
        }
    }
}

void PbrtRendererInfo::fillRendererObjectTypes(
    std::vector<std::string>& renderObjectTypes,
    const std::string& type) const
{
    renderObjectTypes.clear();

    if (type == kFnRendererObjectTypeShader)
    {
        renderObjectTypes.push_back("surface");
    }
    else if (type == kFnRendererObjectTypeRenderOutput)
    {
        renderObjectTypes.push_back(kFnRendererOutputTypeColor);
    }
    // else if (type == kFnRendererObjectTypeOutputChannelCustomParam)
    // {
    //     renderObjectTypes.push_back("opacity");
    // }
}

void PbrtRendererInfo::configureBatchRenderMethod(
    Foundry::Katana::RendererInfo::DiskRenderMethod& batchRenderMethod) const
{
    // e.g.
    //batchRenderMethod.setDebugOutputFileType("txt");
}

std::string PbrtRendererInfo::getRegisteredRendererName() const
{
    return "pbrt";
}

std::string PbrtRendererInfo::getRegisteredRendererVersion() const
{
    return "2.0";
}

std::string PbrtRendererInfo::getRendererObjectDefaultType(
    const std::string& type) const
{
    return FnKat::RendererInfo::RendererInfoBase::getRendererObjectDefaultType(type);
}

bool PbrtRendererInfo::isPresetLocalFileNeeded(
    const std::string &outputType) const
{
    return FnKat::RendererInfo::RendererInfoBase::isPresetLocalFileNeeded(outputType);
}

bool PbrtRendererInfo::isNodeTypeSupported(
    const std::string &nodeType) const
{
    // if (nodeType == "ShadingNode" || nodeType == "OutputChannelDefine")
    // {
    //     return true;
    // }

    // Default:
    return FnKat::RendererInfo::RendererInfoBase::isNodeTypeSupported(nodeType);
}

void PbrtRendererInfo::fillShaderInputNames(
    std::vector<std::string>& shaderInputNames,
    const std::string& shaderName) const
{
    shaderInputNames.clear();
}

void PbrtRendererInfo::fillShaderInputTags(
    std::vector<std::string>& shaderInputTags,
    const std::string& shaderName,
    const std::string& inputName) const
{
    shaderInputTags.clear();
}

void PbrtRendererInfo::fillShaderOutputNames(
    std::vector<std::string>& shaderOutputNames,
    const std::string& shaderName) const
{
    shaderOutputNames.clear();
}

void PbrtRendererInfo::fillShaderOutputTags(
    std::vector<std::string>& shaderOutputTags,
    const std::string& shaderName,
    const std::string& outputName) const
{
    shaderOutputTags.clear();
}

void PbrtRendererInfo::fillRendererShaderTypeTags(
    std::vector<std::string>& shaderTypeTags,
    const std::string& shaderType) const
{
    shaderTypeTags.clear();
    shaderTypeTags.push_back(shaderType);
}

std::string PbrtRendererInfo::getRendererCoshaderType() const
{
    return FnKat::RendererInfo::RendererInfoBase::getRendererCoshaderType();
}

void PbrtRendererInfo::buildLiveRenderControlModules(
    FnKat::GroupBuilder& liveRenderControlModules) const
{
}

bool PbrtRendererInfo::buildRendererObjectInfo(
    FnKat::GroupBuilder& rendererObjectInfo,
    const std::string& name,
    const std::string& type,
    const FnKat::GroupAttribute inputAttr /*= 0x0*/) const
{
    if ( type == kFnRendererObjectTypeRenderOutput)
    {
        std::vector<std::string> typeTags;
        std::string location = name;
        std::string fullPath = "";
        FnKat::Attribute containerHintsAttr;

        configureBasicRenderObjectInfo(rendererObjectInfo,
                                       kFnRendererObjectTypeRenderOutput,                          /* type      */
                                       std::vector<std::string>(typeTags.begin(), typeTags.end()), /* type tags */
                                       location,                                                   /* location  */
                                       fullPath,                                                   /* full path */
                                       kFnRendererObjectValueTypeUnknown,                          /* outputType (unsupported) */
                                       containerHintsAttr);

    }
    if ( type == kFnRendererObjectTypeShader )
    {
        if(shadersMap.find(name) == shadersMap.end())
        {
            _log.error( std::string("Shader ") + name + " not found!" );
            return false;
        }

        // Parse the args file
        std::string fullPath = shadersDir + "/" + name + ".args";
        ArgsFile *argsFile = ArgsFile::parse(fullPath);

        if(!argsFile)
        {
            _log.error(std::string("Couldn't parse ") + fullPath );
            return false;
        }

        // the type tags (just surface for now)
        std::vector<std::string>typeTags;
        typeTags.push_back("surface");

        // Prepare the group builder
        FnKat::Attribute containerHintsAttr;

        configureBasicRenderObjectInfo(rendererObjectInfo,
                                       kFnRendererObjectTypeShader,                                /* type      */
                                       std::vector<std::string>(typeTags.begin(), typeTags.end()), /* type tags */
                                       shadersDir,                                                 /* location  */
                                       fullPath,                                                   /* full path */
                                       kFnRendererObjectValueTypeUnknown,                          /* outputType (unsupported) */
                                       containerHintsAttr);

        // Add the parameters to the group builder
        //TODO: deep search of params
        std::vector<ArgsFileEntry *>::iterator it;
        for (it = argsFile->children.begin(); it != argsFile->children.end(); it++)
        {
            if((*it)->type == "param")
            {
                int paramType = kFnRendererObjectValueTypeInt;
                IntAttribute defaultAttr(3);
                GroupAttribute hintsAttr;
                EnumPairVector enums;

                addRenderObjectParam( rendererObjectInfo,
                                      (*it)->name,
                                      paramType,
                                      0,
                                      defaultAttr,
                                      hintsAttr,
                                      enums );
            }
        }
    }

    return true;
}

void PbrtRendererInfo::initialiseCaches()
{
    initShaders();
}

void PbrtRendererInfo::flushCaches()
{
}

void PbrtRendererInfo::initShaders()
{
    shadersMap.clear();

    char *shaderDirEnv = getenv("PBRT_SHADERPATH");
    if ( shaderDirEnv == NULL )
    {
        return;
    }

    shadersDir = shaderDirEnv;

    DIR *dp;
    struct dirent *dirp;
    if ( (dp  = opendir(shadersDir.c_str())) == NULL )
    {
        std::string msg = std::string("Opening ") + shadersDir;
        _log.warning(msg);
        return;
    }

    std::string file;
    std::string shaderName;
    size_t l;
    while ((dirp = readdir(dp)) != NULL)
    {
        file = dirp->d_name;
        l = file.length();
        if (file != "." && file != "..")
        {
            if ( l > 4 && file.substr(l - 5, l) == ".args")
            {
                shaderName = file.substr(0, l - 5);
                std::string shaderFullPath = shadersDir + "/" + file;
                shadersMap[shaderName] = shaderFullPath;
            }
        }
    }
    closedir(dp);
}

void PbrtRendererInfo::getShaderNames( std::vector<std::string>& names ) const
{
    names.clear();

    std::map<std::string, std::string>::const_iterator it;
    for (it = shadersMap.begin(); it != shadersMap.end(); it++)
    {
        names.push_back(it->first);
    }
}


DEFINE_RENDERERINFO_PLUGIN(PbrtRendererInfo)


void registerPlugins()
{
    REGISTER_PLUGIN(PbrtRendererInfo, "PbrtRendererInfo", 0, 1);
}
